#include "config.h"
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <signal.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "pipes/pipes.h"

static void sig_int_readline(int signo)
{
    rl_kill_full_line(0,0);
    rl_reset_line_state();
    rl_crlf();
    rl_redisplay();
}

int readline_signal()
{
    pipes_set_signal();

    struct sigaction sa2;
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = sig_int_readline;
    sa2.sa_flags |= SA_RESTART;
    if(sigaction(SIGINT, &sa2, NULL) < 0) {
        perror("sigaction2");
        exit(1);
    }
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = SIG_IGN;
    sa2.sa_flags = 0;
    if(sigaction(SIGTSTP, &sa2, NULL) < 0) {
        perror("sigaction2");
        exit(1);
    }
    memset(&sa2, 0, sizeof(sa2));
    sa2.sa_handler = SIG_IGN;
    sa2.sa_flags = 0;
    if(sigaction(SIGUSR1, &sa2, NULL) < 0) {
        perror("sigaction2");
        exit(1);
    }
    sa2.sa_handler = SIG_IGN;
    sa2.sa_flags = 0;
    if(sigaction(SIGQUIT, &sa2, NULL) < 0) {
        perror("sigaction2");
        exit(1);
    }
}

static void main_pipes_job_done(int job_num, char* job_title)
{
    printf("%d: %s done.\n", job_num+1, job_title);
}

static char* prompt()
{
    stack_start_stack();
    pipes_set_signal();

    sObject* fun = FUN_NEW_STACK(NULL);
    sObject* stackframe = UOBJECT_NEW_GC(8, gPipesObject, "_stackframe", FALSE);
    vector_add(gStackFrames, stackframe);
    //uobject_init(stackframe);
    SFUN(fun).mLocalObjects = stackframe;

    sObject* nextout = FD_NEW_STACK();

    int rcode;
    if(!run(gPrompt, gStdin, nextout, &rcode, gCurrentObject, fun)) {
        if(rcode == RCODE_BREAK) {
            fprintf(stderr, "invalid break. Not in a loop\n");
        }
        else if(rcode == RCODE_RETURN) {
            fprintf(stderr, "invalid return. Not in a function\n");
        }
        else if(rcode == RCODE_EXIT) {
            fprintf(stderr, "invalid exit. In the prompt\n");
        }
        else {
            fprintf(stderr, "run time error\n");
            fprintf(stderr, "%s", string_c_str(gErrMsg));
        }
    }
    readline_signal();

    mreset_tty();
    readline_signal();
    char* buf = readline(SFD(nextout).mBuf);

    (void)vector_pop_back(gStackFrames);
    stack_end_stack();

    return buf;
}

void pipes_readline_interface(char** argv, int argc, BOOL no_runtime_script)
{
    pipes_init(kATConsoleApp, no_runtime_script);

    /// start interactive shell ///
    mreset_tty();
    pipes_job_done = main_pipes_job_done;

    /// read histories ///
    char* histfile = getenv("PIPES_HISTFILE");
    if(histfile) {
        read_history(histfile);
    }
    else {
        char* home = getenv("HOME");

        if(home) {
            char path[PATH_MAX];
            snprintf(path, PATH_MAX, "%s/.pipes", home);
            struct stat stat_;
            if(stat(path, &stat_) < 0) {
                mkdir(path, 0755);
            }
            else {
                if(!S_ISDIR(stat_.st_mode)) {
                    fprintf(stderr, "%s is not directory. abort\n", path);
                    exit(1);
                }
            }

            snprintf(path, PATH_MAX, "%s/.pipes/history", home);
            read_history(path);
        }
        else {
            fprintf(stderr, "HOME evironment path is NULL. exited\n");
            exit(1);
        }
    }

    char* histsize_env = getenv("PIPES_HISTSIZE");
    int histsize;
    if(histsize_env) {
        histsize = atoi(histsize_env);
        if(histsize < 0) histsize = 1000;
    }
    else {
        histsize = 1000;
    }

    char* buf;
    HISTORY_STATE* history_state = history_get_history_state();
    int history_num = history_state->length;
    while(1) {
        /// prompt ///
        if(gPrompt) {
            buf = prompt();
        }
        else {
            mreset_tty();
            readline_signal();
            buf = readline("> ");
        }

        /// run ///
        if(buf == NULL) {
            if(vector_size(gJobs) > 0) {
                fprintf(stderr,"\njobs exist\n");
            }
            else {
                break;
            }
        }
        else if(*buf) {
            stack_start_stack();
            int rcode = 0;
            sObject* block = BLOCK_NEW_STACK();
            int sline = 1;
            if(parse(buf, "pipes", &sline, block, NULL, TRUE)) {
                pipes_set_signal();

                sObject* fun = FUN_NEW_STACK(NULL);
                sObject* stackframe = UOBJECT_NEW_GC(8, gPipesObject, "_stackframe", FALSE);
                vector_add(gStackFrames, stackframe);
                //uobject_init(stackframe);
                SFUN(fun).mLocalObjects = stackframe;

                sObject* argv2 = VECTOR_NEW_GC(16, FALSE);
                int i;
                for(i=0; i<argc; i++) {
                    vector_add(argv2, STRING_NEW_GC(argv[i], FALSE));
                }
                hash_put(SFUN(fun).mLocalObjects, "ARGV", argv2);

                SFUN(fun).mRunInfo = NULL;

                if(!run(block, gStdin, gStdout, &rcode, gCurrentObject, fun)) {
                    readline_signal();
                    if(rcode == RCODE_BREAK) {
                        fprintf(stderr, "invalid break. Not in a loop\n");
                    }
                    else if(rcode == RCODE_RETURN) {
                        fprintf(stderr, "invalid return. Not in a function\n");
                    }
                    else if(rcode == RCODE_EXIT) {
                        stack_end_stack();
                        (void)vector_pop_back(gStackFrames);
                        break;
                    }
                    else {
                        fprintf(stderr, "run time error\n");
                        fprintf(stderr, "%s", string_c_str(gErrMsg));
                    }
                }
                readline_signal();

                (void)vector_pop_back(gStackFrames);
            }
            else {
                fprintf(stderr, "parser error\n");
                fprintf(stderr, "%s", string_c_str(gErrMsg));
            }

            /// add history ///
            add_history(buf);
            history_num++;
            if(history_num > histsize) {
                HIST_ENTRY* history = remove_history(0);
                free(history);
            }

            if(rcode != 0) {
                fprintf(stderr, "return code is %d\n", rcode);
            }
            stack_end_stack();
        }

        /// wait background job
        pipes_wait_background_job();

        free(buf);
    }

    if(histfile) {
        write_history(histfile);
    }
    else {
        char* home = getenv("HOME");

        if(home) {
            char path[PATH_MAX];
            snprintf(path, PATH_MAX, "%s/.pipes/history", home);
            write_history(path);
        }
        else {
            fprintf(stderr, "HOME evironment path is NULL. exited\n");
            exit(1);
        }
    }

    pipes_final();
}

void pipes_load_file(char* fname, char** argv, int argc, BOOL no_runtime_script)
{
    pipes_init(kATOptC, no_runtime_script);

    pipes_set_signal_optc();
    sRunInfo runinfo;
    memset(&runinfo, 0, sizeof(sRunInfo));
    runinfo.mSName = "pipes";
    runinfo.mCurrentObject = gRootObject;
    if(!load_file(fname, gStdin, gStdout, &runinfo, argv, argc))
    {
        if(runinfo.mRCode == RCODE_BREAK) {
            fprintf(stderr, "invalid break. Not in a loop\n");
            exit(1);
        }
        else if(runinfo.mRCode == RCODE_RETURN) {
            fprintf(stderr, "invalid return. Not in a function\n");
            exit(1);
        }
        else if(runinfo.mRCode == RCODE_EXIT) {
        }
        else {
            fprintf(stderr, "run time error\n");
            fprintf(stderr, "%s", string_c_str(gErrMsg));
            exit(1);
        }
    }

    if(runinfo.mRCode != 0) {
        fprintf(stderr, "return code is %d\n", runinfo.mRCode);
    }

    pipes_restore_signal_default();
    pipes_final();
}

void pipes_opt_c(char* cmd, char** argv, int argc, BOOL no_runtime_script)
{
    pipes_init(kATOptC, no_runtime_script);

    stack_start_stack();
    int rcode = 0;
    sObject* block = BLOCK_NEW_STACK();
    int sline = 1;
    if(parse(cmd, "pipes", &sline, block, NULL, TRUE)) {
        pipes_set_signal_optc();

        sObject* fun = FUN_NEW_STACK(NULL);
        sObject* stackframe = UOBJECT_NEW_GC(8, gPipesObject, "_stackframe", FALSE);
        vector_add(gStackFrames, stackframe);
        //uobject_init(stackframe);
        SFUN(fun).mLocalObjects = stackframe;

        sObject* argv2 = VECTOR_NEW_GC(16, FALSE);
        int i;
        for(i=0; i<argc; i++) {
            vector_add(argv2, STRING_NEW_GC(argv[i], FALSE));
        }
        hash_put(SFUN(fun).mLocalObjects, "ARGV", argv2);

        if(!run(block, gStdin, gStdout, &rcode, gRootObject, fun)) {
            if(rcode == RCODE_BREAK) {
                fprintf(stderr, "invalid break. Not in a loop\n");
            }
            else if(rcode == RCODE_RETURN) {
                fprintf(stderr, "invalid return. Not in a function\n");
            }
            else if(rcode == RCODE_EXIT) {
            }
            else {
                fprintf(stderr, "run time error\n");
                fprintf(stderr, "%s", string_c_str(gErrMsg));
            }
        }
        pipes_restore_signal_default();
        (void)vector_pop_back(gStackFrames);
    }
    else {
        fprintf(stderr, "parser error\n");
        fprintf(stderr, "%s", string_c_str(gErrMsg));
    }

    if(rcode != 0) {
        fprintf(stderr, "return code is %d\n", rcode);
    }
    stack_end_stack();

    pipes_final();
}

